Windows Profile Creation with Visual BASIC Tim Jones 11/9/91 Among the vast number of challenges that a Windows application author can run into, usage of the .INI profile files can be confusing, at least, and down right daunting, at worst. The profile settings are a part of Windows and most users take them for granted - until they run into an application that requires that some minor change be made each time they start the application. When creating a Windows application, the author should decide if there are any settings that a user can change that may be static for most operational sessions. If so, the application should use one of the .INI profile files supported by Windows. Using the profile mechanisms, you can make favorite settings appear as if they were defaults and make the use of the application easier. Windows currently supports two types of .INI files, a public file called WIN.INI and a application-specific, or private, .INI file. Windows reads the WIN.INI file each time you start up, so if your application's settings affect Windows or other Applications, you should place your application profile entries here. If the settings for your application affect only the application itself, you should use a private .INI file. More authors should use the private file if my WIN.INI file is any example - there are currently 313 lines in my WIN.INI file, of which, 173 do not directly affect Windows or other Windows applications. An entry in either .INI file consists of a section name, in square brackets, followed by a list of keys and their assigned values: [VBTerm] PORT=2 BAUD=19200 DATA=8 STOP=0 PARITY=N Current System=3 In this example, the section name is "VBTerm" while "PORT" and "BAUD" are keys. All entries are case-insensitive - in other words, PARITY = Parity = PaRIty, so the choice of case when creating the entries is just for appearance sake. Additionally, the same key may appear in different sections of the same .INI file without fear of overwriting each other. The Windows API calls for writing and reading entries in one or the other of these .INI files are: WIN.INI PRIVATE.INI WriteProfileString WritePrivateProfileString GetProfileString GetPrivateProfileString GetProfileInt GetPrivateProfileInt Each of these items requires the name of the section (an application entry can consist of more than one section entry as this is managed by the application), the key name, its value for a write or a pointer to a variable to hold its returned value on a read, the default setting and number of bytes to read for a read and the name of the .INI file in either case if using the private functions. Here are the declares for WriteProfileString and ReadProfileString and some examples: Declare Function WriteProfileString Lib "Kernel" (ByVal lpAppName As String, ByVal lpKeyName As String, ByVal lpString As String) As Integer Declare Function GetProfileString Lib "Kernel" (ByVal lpAppName As String, ByVal lpKeyName As String, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Integer) As Integer Result% = WriteProfileString("Section Name", "Key Name", KeySetting$) Result% is actually a BOOLEAN return (either TRUE or FALSE (for the sake of simplicity, I will define TRUE as non-Zero here)) and can be discarded after examination. It will be TRUE (non-Zero) if the operation was a success and FALSE (Zero) if it failed (see below). Result% = GetProfileString("Section Name", "Key Name", Default$, Destination$, 5) In this case, Result% will contain the value of the actual number of bytes read, up to the maximum value (5 in this example), or Zero if the operation failed. A failed attempt here usually means that either the .INI file did not exist (in the event of a private file) or the key was not defined in the named section. Since this should not be a show stopper, the value in Default$ will be assigned to Destination$. This is the way to assign a value even when the entries have not been made because of a first run of the application. Since both of these examples used the public WIN.INI file for entries, it is not necessary to specify the name of the .INI file from which to read. In the event of a private .INI file, you would add the name of the .INI file as the last parameter of the WritePrivateProfileString and other private functions. This name can either be just the filename or a fully qualified path and filename. If no path is specified, the file is searched for in the Windows directory When using the WritePrivateProfileString function, if no path is specified, the named .INI file will be created if it does not already exist. If a path is specified, however, the file will not be created. This should be the only reason for a FALSE return from a call to this function. If the key exists, the existing value is replaced by the new value. Also, sending NULL ("") as the lpString value will delete the key from the section. Sending a string name that contains NULL data (i.e. Alpha$ = "") will just delete the value assigned to key. One important reminder about the way that strings are handled when used in an API function in which the string contents are changed - the string must be initialized before the API function is called. I learned this the hard way. When I read a .INI's string entry into an uninitialized string, I ended up with a UAE. Costas Kitsos and others on Compuserve's MSBASIC forum provided the kick in the head required and the code sample below is now bullet proof. Here are sections of code that will demonstrate how to use these functions: ----------------------------- ' Initialize the string variable to 2 bytes (N, E, or O and the NULL) TParity = String$(2, 0) ' Check to see if entries have been made. If TPort comes back 0, then ' no VBTERM.INI file exists. Since TPort is an Integer, we can get its ' value directly using GetPrivateProfileInt instead of using ' GetPrivateProfileString and converting the returned string. TPort = GetPrivateProfileInt("VBTerm", "PORT", 0, "VBTERM.INI") ' If TPort is 1 or 2, then we continue to process the .INI entries If TPort <> 0 Then TBaud = GetPrivateProfileInt("VBTerm", "BAUD", 2400, "VBTERM.INI") TWord = GetPrivateProfileInt("VBTerm", "DATA", 8, "VBTERM.INI") TStop = GetPrivateProfileInt("VBTerm", "STOP", 1, "VBTERM.INI") R% = GetPrivateProfileString ("VBTerm", "PARITY", "N", TParity, 2, "VBTERM.INI") ' When R% is returned, it will indicate how many characters were actually ' read by the call to GetPrivateProfileInt. We force the function to only get ' two characters by the fifth entry in the parameter list. Else ' Otherwise, we bring up the configuration editor Config.Show MODAL End If ' Once we're done, we save the values into the .INI file so we are ready ' to go next time. We also call this if Config is run again. Note that we ' don't have a WritePrivateProfileInt function! In this case, R% is a ' throw-away return as described in the text above. R% = WritePrivateProfileString("VBTerm", "PORT", Str$(TPort), "VBTERM.INI") R% = WritePrivateProfileString("VBTerm", "BAUD", Str$(TBaud), "VBTERM.INI") R% = WritePrivateProfileString("VBTerm", "DATA", Str$(TWord), "VBTERM.INI") R% = WritePrivateProfileString("VBTerm", "STOP", Str$(TStop), "VBTERM.INI") R% = WritePrivateProfileString("VBTerm", "PARITY", TParity, "VBTERM.INI") ----------------------------- This example has indicated the requirements for a private .INI file. Also, note that the name, in this case VBTERM.INI, can be anything you want. Using some form of the application name is best unless you are into confusing your users. If you have any questions, you may reach me on Compuserve as 70750,701. Good programming!